package tman.simulator.snapshot;

import common.configuration.Global;
import common.peer.PeerAddress;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeMap;

import javax.xml.parsers.ParserConfigurationException;

import org.jfree.ui.RefineryUtilities;
import org.xml.sax.SAXException;

//import cyclon.simulator.snapshot.PeerInfo;

import tman.main.GraphPanel;
import tman.main.GraphPanel2;
import tman.main.GraphPanel3;
import tman.main.GraphPanel4;
//import tman.main.OverlayPanel;
import tman.simulator.snapshot.GraphMLGenerator;


public class Snapshot {
        private static HashMap<PeerAddress, PeerInfo> peers = new HashMap<PeerAddress, PeerInfo>();
    	private static HashMap<PeerAddress, Integer> fanout = new HashMap<PeerAddress, Integer>();
    	private static HashMap<PeerAddress, Integer> fanin = new HashMap<PeerAddress, Integer>();
    	private static HashMap<Integer, Double> PathLength = new HashMap<Integer, Double>();
    	private static HashMap<Integer, Double> InDegree = new HashMap<Integer, Double>();
    	private static HashMap<Integer, Double> OutDegree = new HashMap<Integer, Double>();
    	private static HashMap<Integer, Integer> HopCountDistr = new HashMap<Integer, Integer>();
    	private static HashMap<Integer, Integer> OptimalHopCountDistr = new HashMap<Integer, Integer>();
    	private static HashMap<Integer, HashMap<Integer, Integer>> DetailedHopCountDistr = new HashMap<Integer, HashMap<Integer, Integer>>();
    	private static HashMap<Integer, Double> ErrorRate = new HashMap<Integer, Double>();
    	private static HashMap<PeerAddress, Float> avgTmanAge = new HashMap<PeerAddress, Float>();
    	private static HashMap<PeerAddress, Float> avgCyclonAge = new HashMap<PeerAddress, Float>();
    	private static HashMap<PeerAddress, Float> avgTmanAccuracy = new HashMap<PeerAddress, Float>();
    	private static HashMap<PeerAddress, Float> avgCyclonAccuracy = new HashMap<PeerAddress, Float>();
        private static int counter = 0;
    	private static GraphUtil g;
        private static String FILENAME = "tman.out";

        //-------------------------------------------------------------------
        public static void init(int numOfStripes) {
                FileIO.write("", FILENAME);
        }

        //-------------------------------------------------------------------
        public static void addPeer(PeerAddress address) {
                peers.put(address, new PeerInfo(address));
        		fanin.put(address, 0);
        		fanout.put(address, 0);
        }
        
        public static void setavgTmanAge(PeerAddress self, Float age)
        {
        	avgTmanAge.put(self, age);
        }
        
        public static void setavgCyclonAge(PeerAddress self, Float age)
        {
        	avgCyclonAge.put(self, age);
        }
        
        public static void setavgTmanAccuracy(PeerAddress self, Float acc)
        {
        	avgTmanAccuracy.put(self, acc);
        }
        
        public static void setavgCyclonAccuracy(PeerAddress self, Float acc)
        {
        	avgCyclonAccuracy.put(self, acc);
        }


        //-------------------------------------------------------------------
        public static void removePeer(PeerAddress address) {
                peers.remove(address);
                fanin.remove(address);
                fanout.remove(address);
        }
        
        public static ArrayList<PeerAddress> getPeerParents(PeerAddress address) {
            PeerInfo peerInfo = peers.get(address);
            return peerInfo.getParents();
        }
        
        public static Integer getHopCount(PeerAddress address) {
            PeerInfo peerInfo = peers.get(address);
            return peerInfo.getHopCount();
        }
        
        public static Integer getSecondHopCount(PeerAddress address) {
            PeerInfo peerInfo = peers.get(address);
            return peerInfo.getSecondHopCount();
        }
        
        public static void setHopCount(PeerAddress address, Integer hopcount) {
            PeerInfo peerInfo = peers.get(address);
            if (peerInfo == null)
            	return;
            peerInfo.setHopCount(hopcount);
        }
        
        public static void setSecondHopCount(PeerAddress address, Integer hopcount) {
            PeerInfo peerInfo = peers.get(address);
            if (peerInfo == null)
            	return;
            peerInfo.setSecondHopCount(hopcount);
        }

        //-------------------------------------------------------------------
        public static void updateSuccPred(PeerAddress address, PeerAddress succ, PeerAddress pred) {
                PeerInfo peerInfo = peers.get(address);

                if (peerInfo == null)
                        return;

                peerInfo.updateSuccPred(succ, pred);
        }
        
        public static void updateParents(PeerAddress address, ArrayList<PeerAddress> parents) {
            PeerInfo peerInfo = peers.get(address);

            if (peerInfo == null)
                    return;

            peerInfo.updateParents(parents);
        }
        
        public static void updateChildren(PeerAddress address, ArrayList<PeerAddress> children) {
            PeerInfo peerInfo = peers.get(address);

            if (peerInfo == null)
                    return;

            peerInfo.updateChildren(children);
        }

        public static void updateSmallWorldPartners(PeerAddress address, ArrayList<PeerAddress> partners) {
                PeerInfo peerInfo = peers.get(address);

                if (peerInfo == null)
                        return;

                peerInfo.updateSmallWorldPartners(partners);
        }

        //-------------------------------------------------------------------
        public static void updateTManPartners(PeerAddress address, ArrayList<PeerAddress> partners) {
                PeerInfo peerInfo = peers.get(address);           
                if (peerInfo == null)
                        return;
                //System.out.println("I'm " + address.getPeerId() + " TmanPartners: " + partners);
                peerInfo.updateTManPartners(partners);
        }

        //-------------------------------------------------------------------
        public static void updateCyclonPartners(PeerAddress address, ArrayList<PeerAddress> partners) {
                PeerInfo peerInfo = peers.get(address);

                if (peerInfo == null)
                        return;
                //System.out.println("I'm " + address.getPeerId() + " CyclonPartners: " + partners);
                peerInfo.updateCyclonPartners(partners);
                fanout.put(address, partners.size());
        }

        //-------------------------------------------------------------------
        
        public static String customreport()
        {
        	String str = "";
        	PeerInfo peerInfo;
            for (PeerAddress d : peers.keySet())            
            {	peerInfo = peers.get(d);
        		ArrayList<PeerAddress> children = peerInfo.getChildren();
        		ArrayList<PeerAddress> cyclonPartners = peerInfo.getCyclonPartners();
        		str += "I'm " + d.getPeerId() + ":" + d.getBandwidth() + " HC:" + peerInfo.getHopCount() + " These are my children: [";
	            for(int i=0; i<children.size(); i++)
	            {
	            	str += children.get(i).getPeerId() + ":" + children.get(i).getBandwidth();
	            	if(i != children.size()-1)
	            		str += ", ";

	            }
            	str += "] and These are my cyclon partners: [";
	            for(int i=0; i<cyclonPartners.size(); i++)
	            {
	            	str += cyclonPartners.get(i).getPeerId() + ":" + cyclonPartners.get(i).getBandwidth();
	            	if(i != cyclonPartners.size()-1)
	            		str += ", ";

	            }
	            str += "] \n";
        	}
            return str;
        }
        
        public static String ageReport() {
        	Float avgTmanAges = (float) 0.0;
            Float avgCyclonAges = (float) 0.0;
        	for(PeerAddress pa: avgTmanAge.keySet())
        	{
        		avgTmanAges += avgTmanAge.get(pa);
        	}
        	
        	for(PeerAddress pa: avgCyclonAge.keySet())
        	{
        		avgCyclonAges += avgCyclonAge.get(pa);
        	}
        	
        	if(avgCyclonAge.keySet().size() != 0)
        		avgCyclonAges = avgCyclonAges / avgCyclonAge.keySet().size();
        	if(avgTmanAge.keySet().size() != 0)
        		avgTmanAges = avgTmanAges / avgTmanAge.keySet().size();
        	String str = "Average Tman view ages: " + avgTmanAges + "\n";
        	str += "Average Cyclon view ages: " + avgCyclonAges + "\n";
        	
        	return str;
        }
        
        public static String accuracyReport() {
        	Float avgTmanAccs = (float) 0.0;
            Float avgCyclonAccs = (float) 0.0;
        	for(PeerAddress pa: avgTmanAccuracy.keySet())
        	{
        		avgTmanAccs += avgTmanAccuracy.get(pa);
        	}
        	
        	for(PeerAddress pa: avgCyclonAccuracy.keySet())
        	{
        		avgCyclonAccs += avgCyclonAccuracy.get(pa);
        	}
        	
        	if(avgCyclonAccuracy.keySet().size() != 0)
        		avgCyclonAccs = avgCyclonAccs / avgCyclonAccuracy.keySet().size();
        	if(avgTmanAccuracy.keySet().size() != 0)
        		avgTmanAccs = avgTmanAccs / avgTmanAccuracy.keySet().size();
        	String str = "Average Tman view accs: " + avgTmanAccs + "\n";
        	str += "Average Cyclon view accs: " + avgCyclonAccs + "\n";
        	
        	return str;
        }
        
        public static void report() {
                PeerAddress[] peersList = new PeerAddress[peers.size()];
                peers.keySet().toArray(peersList);
                
                if(counter == 0)
                {
                	CentralSolution LetsSolveThis = new CentralSolution();
                	for(Integer i: Global.odDistr.keySet())
                	{
                		System.out.println("OD frequency of " + i + " is: " + Global.odDistr.get(i));
                    	LetsSolveThis.addNodes(i, Global.odDistr.get(i));
                	}
                	
                	LetsSolveThis.addNodes(9, 1);
                	LetsSolveThis.startAndRun();
                	OptimalHopCountDistr = LetsSolveThis.getDistr();
                }
                
                HopCountDistr = new HashMap<Integer, Integer>();
                
            	for(int i=0; i<peersList.length; i++)
            	{
            		PeerInfo peerInfo = peers.get(peersList[i]);
            		Integer hc = peerInfo.getHopCount();
            		if(HopCountDistr.get(hc) == null)
            			HopCountDistr.put(hc, 1);
            		else
            			HopCountDistr.put(hc, HopCountDistr.get(hc) + 1);
            	}
            	
            	Double totalerror = 0.0;
            	Double denom = 0.0;
            	for(Integer od: OptimalHopCountDistr.keySet())
            	{
            		int diff = 0;
            		int freq = OptimalHopCountDistr.get(od);
            		if(HopCountDistr.get(od) != null)
            		{
            			diff = Math.abs(freq - HopCountDistr.get(od));    					
            		}
            		else diff = freq;
            		
            		denom += freq;
            		totalerror += diff;
            	}
            	totalerror = totalerror/denom*100.0;
            	ErrorRate.put(counter, totalerror);

                String str = new String();
                str += "current time: " + counter++ + "\n";
                str += "Error Rate: " + totalerror + "\n";
                str += "Total Peers: " + peers.size() + "\n";
                str += ageReport();
                str += accuracyReport();
                //str += reportNetworkState();
                //str += customreport();
                //str += reportDetailes();         
                str += "###\n";
                
                reportGraphStat();

                System.out.println(str);
                FileIO.append(str, FILENAME);
                if(counter > Global.r)
                {
                	//System.exit(0);
                }
                
                
                 
                if(counter > Global.r){
                	
                	calcHopCountDistr();
                	calcDetailedHopCountDistr();
                	
                	System.out.println("Number of Rounds: " + Global.r);
                	
        	        final GraphPanel g1 = new GraphPanel("Simulation Statistics", PathLength, HopCountDistr, OptimalHopCountDistr);
        	        g1.pack();
        	        RefineryUtilities.centerFrameOnScreen(g1);
        	        g1.setVisible(true);
        	        //------------------------
        	        final GraphPanel3 g3 = new GraphPanel3("Detailed HopCount Distr.", DetailedHopCountDistr);
        	        g3.pack();
        	        RefineryUtilities.centerFrameOnScreen(g3);
        	        g3.setVisible(true);
        	        //------------------------
        	        final GraphPanel4 g4 = new GraphPanel4("Error Rate", ErrorRate);
        	        g4.pack();
        	        RefineryUtilities.centerFrameOnScreen(g4);
        	        g4.setVisible(true);
        	        //------------------------
                    Thread thisThread = Thread.currentThread();
        			thisThread.stop();


            } 
        }
        
        //-------------------------------------------------------------------
        private static void calcHopCountDistr() {
            PeerAddress[] peersList = new PeerAddress[peers.size()];
            peers.keySet().toArray(peersList);
            
        	HopCountDistr = new HashMap<Integer, Integer>();
         	
        	for(int i=0; i<peersList.length; i++)
        	{
        		PeerInfo peerInfo = peers.get(peersList[i]);
        		Integer hc = peerInfo.getHopCount();
        		if(HopCountDistr.get(hc) == null)
        			HopCountDistr.put(hc, 1);
        		else
        			HopCountDistr.put(hc, HopCountDistr.get(hc) + 1);
        	}
        }
        
        private static void calcDetailedHopCountDistr() {
            PeerAddress[] peersList = new PeerAddress[peers.size()];
            peers.keySet().toArray(peersList);
            
        	for(int i=0; i<peersList.length; i++)
        	{
        		PeerInfo peerInfo = peers.get(peersList[i]);
        		Integer hc = peerInfo.getHopCount();
        		Integer OD = peersList[i].getBandwidth();
        		if(DetailedHopCountDistr.get(hc) == null)
        		{
        			HashMap<Integer, Integer> temp = new HashMap<Integer, Integer>();
        			temp.put(OD, 1);
        			DetailedHopCountDistr.put(hc, temp);
        		}
        		else
        		{
        			HashMap<Integer, Integer> temp = DetailedHopCountDistr.get(hc);
        			if(temp.get(OD)==null)
        				temp.put(OD, 1);
        			else
        				temp.put(OD, temp.get(OD)+1);
        			DetailedHopCountDistr.put(hc, temp);
    
        		}
        		
        	}
        }

        //-------------------------------------------------------------------
        private static String reportNetworkState() {
                String str = new String("---\n");
                int totalNumOfPeers = peers.size();
                str += "total number of peers: " + totalNumOfPeers + "\n";

                return str;            
        }
        
    	private static String reportGraphStat() {
    		String str = new String("---\n");
    		double id, od, cc, pl, istd, cs;
    		int diameter;
    		
    		g = new GraphUtil(peers);
    		id = g.getMeanInDegree();
    		istd = g.getInDegreeStdDev();
    		od = g.getMeanOutDegree();
    		cc = g.getMeanClusteringCoefficient();
    		pl = g.getMeanPathLength();
    		//cs = g.getMaxClusterSize();
    		diameter = g.getDiameter();
    		
    		PathLength.put(counter, pl);
    		InDegree.put(counter, id);
    		OutDegree.put(counter, od);
    		
    		str += "graph statistics: " + "\n";
    		str += "Diameter: " + diameter + "\n";
    		str += "Average path length: " + String.format("%.4f", pl) + "\n";
    		str += "Clustering-coefficient: " + String.format("%.4f", cc) + "\n";
    		str += "Average in-degree: " + String.format("%.4f", id) + "\n";
    		str += "In-degree standard deviation: " + String.format("%.4f", istd) + "\n";
    		str += "Average out-degree: " + String.format("%.4f", od) + "\n";
    		//str += "Biggest cluster size: " + cs + "\n";
    		
    		return str;
    	}
    	

        //-------------------------------------------------------------------
        private static String reportDetailes() {
                PeerInfo peerInfo;
                String str = new String("---\n");

                for (PeerAddress peer : peers.keySet()) {
                        peerInfo = peers.get(peer);
                        if(peer.isSource())
                        {
                        	str += "<Source> ";
                        }
                        str += "peer: " + peer + ":" + peer.getBandwidth() + ", HC:" + peerInfo.getHopCount();
                        //str += ", cyclon partners: " + peerInfo.getCyclonPartners();
                        str += ", tman partners: " + peerInfo.getTManPartners();
                        str += ", children: [";

                        for (PeerAddress child: peerInfo.getChildren())
                        {
                        	str += child.getPeerId() + ":" + child.getBandwidth();
                        	str += ", ";
                        }
                    	str += "]";
                        str += ", parents: [";
                        for (PeerAddress parent: peerInfo.getParents())
                        {
                        	str += parent.getPeerId() + ":" + parent.getBandwidth();
                        	str += ", ";
                        }
                    	str += "]";
                        str += "\n";
                }

                return str;
        }
               
      //-------------------------------------------------------------------
    	private static HashMap<Integer, Integer> reportFanoutHistogram() {
    		HashMap<Integer, Integer> fanoutHistogram = new HashMap<Integer, Integer>();
    		String str = new String("---\n");

    		Integer n;
    		for (Integer num : fanout.values()) {
    			n = fanoutHistogram.get(num);
    			
    			if (n == null)
    				fanoutHistogram.put(num, 1);
    			else
    				fanoutHistogram.put(num, n + 1);
    			
    		}
    		
    		//str += "out-degree: " + fanoutHistogram.toString() + "\n";
    		
    		return fanoutHistogram;
    	}

    //-------------------------------------------------------------------
    	private static HashMap<Integer, Integer> reportFaninHistogram() {
    		HashMap<Integer, Integer> faninHistogram = new HashMap<Integer, Integer>();
    		String str = new String("---\n");

    		int count;
    		for (PeerAddress node : fanin.keySet()) {
    			count = 0;
    			for (PeerInfo peerInfo : peers.values()) {
    				if (peerInfo.getCyclonPartners() != null && peerInfo.isCyclonPartner(node))
    					count++;					
    			}
    			
    			fanin.put(node, count);
    		}

    		Integer n;
    		for (Integer num : fanin.values()) {
    			n = faninHistogram.get(num);
    			
    			if (n == null)
    				faninHistogram.put(num, 1);
    			else
    				faninHistogram.put(num, n + 1);
    			
    		}
    		
    		//str += "in-degree: " + faninHistogram.toString() + "\n";
    		
    		return faninHistogram;
    	}
    //-------------------------------------------------------------------
}

